<template>
  <div class="wrap">
    <!-- 工具栏：直接可见的交互开关 -->
    <div class="toolbar">
      <label
        ><input type="checkbox" v-model="useEdgeMiddles" /> 使用边中点</label
      >
      <label><input type="checkbox" v-model="showRays" /> 显示射线</label>
      <label><input type="checkbox" v-model="autoRotate" /> 自动旋转相机</label>

      <label style="display: flex; align-items: center; gap: 6px">
        求交方式：
        <select v-model="intersectionMode">
          <option value="analytic">
            解析式 t = -ori.y/dir.y（与你函数一致）
          </option>
          <option value="plane">Ray.intersectPlane（更稳健）</option>
        </select>
      </label>

      <label style="display: flex; align-items: center; gap: 6px">
        地面高度 y =
        <input
          type="range"
          min="-20"
          max="20"
          step="0.5"
          v-model.number="planeY"
        />
        <span>{{ planeY.toFixed(1) }}</span>
      </label>

      <button class="btn" @click="recenter">重置视角</button>
    </div>

    <!-- three 容器 -->
    <div ref="stage" class="stage"></div>

    <!-- HUD：解释 el 是什么，并展示尺寸、命中信息 -->
    <div class="hud">
      <div>
        <b>el</b>（renderer.domElement）：<code>&lt;{{ elTag }}&gt;</code>
      </div>
      <div>
        el.clientWidth × el.clientHeight：<b>{{ elW }}</b> × <b>{{ elH }}</b> px
      </div>
      <div>采样点数：{{ sampleCount }}，命中点数：{{ hitCount }}</div>
      <div :style="{ color: hitCount === 0 ? '#ff7b7b' : '#9ae6b4' }">
        {{
          hitCount === 0 ? "⚠ 无交点（相机可能在平面下方或朝上）" : "✅ 正常"
        }}
      </div>
      <div style="opacity: 0.8; margin-top: 6px">
        注：屏幕像素点 (x,y) 先按 el 尺寸换算到 NDC (-1~1)，再用 Raycaster
        从相机发射射线，和 y=planeY 的平面求交。
      </div>
    </div>
  </div>
</template>

<script setup>
// ------------- 基本依赖（JS 版） -------------
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

// 响应式状态
const stage = ref(null);
const useEdgeMiddles = ref(true); // 四角 + 边中点（更稳）
const showRays = ref(true);
const autoRotate = ref(true); // 默认自动旋转 → 页面必有反馈
const intersectionMode = ref("analytic"); // 'analytic' | 'plane'
const planeY = ref(0); // y=0 -> y=任意高度

// HUD
const elTag = ref("-");
const elW = ref(0);
const elH = ref(0);
const sampleCount = ref(0);
const hitCount = ref(0);

// three 实例
let scene, camera, renderer, controls;
let grid,
  axes,
  rayGroup,
  hitGroup,
  footprintHullFill,
  footprintHullLine,
  cornersLoop;
let raycaster, plane;
let raf = 0;
const clock = new THREE.Clock();

// ---- 采样点（四角 + 可选边中点，使用 DOM 像素坐标）----
function getSamplePoints(w, h, withMids) {
  const base = [
    { x: 0, y: 0 },
    { x: w, y: 0 },
    { x: w, y: h },
    { x: 0, y: h },
  ];
  if (!withMids) return base;
  const mids = [
    { x: w / 2, y: 0 },
    { x: w, y: h / 2 },
    { x: w / 2, y: h },
    { x: 0, y: h / 2 },
  ];
  return base.concat(mids);
}

// ---- 单调链凸包（按 xz 平面）----
function convexHullXZ(points) {
  if (!points || points.length <= 2) return points || [];
  const pts = points
    .map((p) => new THREE.Vector2(p.x, p.z))
    .sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
  const cross = (o, a, b) =>
    (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
  const lower = [];
  for (const p of pts) {
    while (
      lower.length >= 2 &&
      cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0
    )
      lower.pop();
    lower.push(p);
  }
  const upper = [];
  for (let i = pts.length - 1; i >= 0; i--) {
    const p = pts[i];
    while (
      upper.length >= 2 &&
      cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0
    )
      upper.pop();
    upper.push(p);
  }
  const hull2 = lower.slice(0, -1).concat(upper.slice(0, -1));
  return hull2.map((v2) => new THREE.Vector3(v2.x, 0, v2.y));
}

// ---- 与 y=planeY 求交（两种方式可切换）----
function intersectWithPlane(ray, planeYValue) {
  if (intersectionMode.value === "analytic") {
    // 解析式：t = -(ori.y - planeY) / dir.y
    const ori = ray.origin,
      dir = ray.direction;
    const t = -(ori.y - planeYValue) / dir.y;
    if (t > 0 && Number.isFinite(t)) {
      const hit = ori.clone().add(dir.clone().multiplyScalar(t));
      return hit;
    }
    return null;
  } else {
    // 标准方式：Ray.intersectPlane
    plane.set(new THREE.Vector3(0, 1, 0), -planeYValue); // n·x + d = 0 -> d = -planeY
    const out = new THREE.Vector3();
    return ray.intersectPlane(plane, out);
  }
}

// ---- 核心：模拟你这段 _getGroundFootprint 的计算 ----
function computeFootprintPoints() {
  const el = renderer.domElement;
  // 收集 HUD 信息：el 是个 <canvas>
  elTag.value = el.tagName.toLowerCase();
  elW.value = el.clientWidth;
  elH.value = el.clientHeight;

  const w = el.clientWidth,
    h = el.clientHeight;
  if (h === 0) return [];

  const samples = getSamplePoints(w, h, useEdgeMiddles.value);
  sampleCount.value = samples.length;

  const res = [];
  for (const p of samples) {
    // 像素坐标 -> NDC（-1..1）
    const ndc = new THREE.Vector2((p.x / w) * 2 - 1, -(p.y / h) * 2 + 1);
    // 通过该 NDC 从相机发射射线
    raycaster.setFromCamera(ndc, camera);
    const ray = raycaster.ray.clone();

    // 与 y=planeY 的水平面求交（两种方法可切换）
    const hit = intersectWithPlane(ray, planeY.value);
    if (hit) {
      // 为了避免与网格 Z-fighting，稍微抬高
      res.push(new THREE.Vector3(hit.x, planeY.value + 0.001, hit.z));
    }
  }
  hitCount.value = res.length;
  return res;
}

// ---- 绘制：凸包填充 + 外轮廓线 +（额外）四角轮廓对比 + 命中点 + 射线 ----
function updateDrawing() {
  // 1) 计算命中点
  const points = computeFootprintPoints();

  // 清理点位组、射线组
  hitGroup.clear();
  rayGroup.clear();

  // 2) 若有命中点：绘制凸包面与线
  if (points.length >= 3) {
    const hull = convexHullXZ(points);

    // 填充（绿色半透明）
    {
      const shape = new THREE.Shape(
        hull.map((v) => new THREE.Vector2(v.x, v.z))
      );
      const geom = new THREE.ShapeGeometry(shape);
      if (!footprintHullFill) {
        const mat = new THREE.MeshBasicMaterial({
          color: 0x00ff9d,
          transparent: true,
          opacity: 0.22,
          depthWrite: false,
          depthTest: false,
        });
        footprintHullFill = new THREE.Mesh(geom, mat);
        footprintHullFill.position.y = planeY.value + 0.0015;
        scene.add(footprintHullFill);
      } else {
        footprintHullFill.geometry.dispose();
        footprintHullFill.geometry = geom;
        footprintHullFill.position.y = planeY.value + 0.0015;
        footprintHullFill.visible = true;
      }
    }

    // 轮廓线（白色）
    {
      const pos = new Float32Array((hull.length + 1) * 3);
      for (let i = 0; i < hull.length; i++) {
        pos[i * 3 + 0] = hull[i].x;
        pos[i * 3 + 1] = planeY.value + 0.002;
        pos[i * 3 + 2] = hull[i].z;
      }
      pos[hull.length * 3 + 0] = hull[0].x;
      pos[hull.length * 3 + 1] = planeY.value + 0.002;
      pos[hull.length * 3 + 2] = hull[0].z;

      if (!footprintHullLine) {
        const geom = new THREE.BufferGeometry();
        geom.setAttribute("position", new THREE.BufferAttribute(pos, 3));
        const mat = new THREE.LineBasicMaterial({
          color: 0xffffff,
          transparent: true,
          opacity: 0.9,
          depthTest: false,
        });
        footprintHullLine = new THREE.Line(geom, mat);
        scene.add(footprintHullLine);
      } else {
        footprintHullLine.geometry.setAttribute(
          "position",
          new THREE.BufferAttribute(pos, 3)
        );
        footprintHullLine.geometry.computeBoundingSphere();
        footprintHullLine.visible = true;
      }
    }
  } else {
    if (footprintHullFill) footprintHullFill.visible = false;
    if (footprintHullLine) footprintHullLine.visible = false;
  }

  // 3) 额外：只用四角画一个虚线环，便于理解“视锥四角投影”
  drawCornerLoop();

  // 4) 画命中点（小球）
  const dotGeo = new THREE.SphereGeometry(1, 12, 12);
  const dotMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
  for (const p of points) {
    const m = new THREE.Mesh(dotGeo, dotMat);
    m.position.copy(p).setY(planeY.value + 0.004);
    hitGroup.add(m);
  }

  // 5) （可选）画射线：相机位置 -> 命中点
  if (showRays.value) {
    const mat = new THREE.LineDashedMaterial({
      color: 0xffffff,
      dashSize: 1,
      gapSize: 0.6,
      transparent: true,
      opacity: 0.6,
      depthTest: false,
    });
    for (const p of points) {
      const geo = new THREE.BufferGeometry().setFromPoints([
        camera.position.clone(),
        new THREE.Vector3(p.x, planeY.value, p.z),
      ]);
      const line = new THREE.Line(geo, mat);
      line.computeLineDistances();
      rayGroup.add(line);
    }
  }
}

// ---- 只用四角画一个虚线闭合线，帮助理解“屏幕四角的投影” ----
function drawCornerLoop() {
  const el = renderer.domElement;
  const w = el.clientWidth,
    h = el.clientHeight;
  const corners = [
    { x: 0, y: 0 },
    { x: w, y: 0 },
    { x: w, y: h },
    { x: 0, y: h },
  ];
  const hits = [];
  for (const p of corners) {
    const ndc = new THREE.Vector2((p.x / w) * 2 - 1, -(p.y / h) * 2 + 1);
    raycaster.setFromCamera(ndc, camera);
    const ray = raycaster.ray.clone();
    const hit = intersectWithPlane(ray, planeY.value);
    if (hit) hits.push(new THREE.Vector3(hit.x, planeY.value + 0.0025, hit.z));
  }

  if (hits.length < 3) {
    if (cornersLoop) cornersLoop.visible = false;
    return; 
  }

  // 按 0→1→2→3→0 顺序画一个虚线框
  const order = [0, 1, 2, 3, 0].filter((i) => i < hits.length);
  const pts = order.map((i) => hits[i]);
  const geo = new THREE.BufferGeometry().setFromPoints(pts);
  if (!cornersLoop) {
    const mat = new THREE.LineDashedMaterial({
      color: 0x7ad1ff,
      dashSize: 1.2,
      gapSize: 0.6,
      transparent: true,
      opacity: 0.9,
      depthTest: false,
    });
    cornersLoop = new THREE.Line(geo, mat);
    cornersLoop.computeLineDistances();
    scene.add(cornersLoop);
  } else {
    cornersLoop.geometry.dispose();
    cornersLoop.geometry = geo;
    cornersLoop.computeLineDistances();
    cornersLoop.visible = true;
  }
}

// ---- 初始化 three 场景 ----
function initThree() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x0f1116);

  camera = new THREE.PerspectiveCamera(60, 16 / 9, 0.1, 2000);
  camera.position.set(60, 60, 60);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(stage.value.clientWidth, stage.value.clientHeight);
  stage.value.appendChild(renderer.domElement);

  // 可见描边帮助识别 el（canvas）
  renderer.domElement.style.outline = "1px solid #2c3646";
  renderer.domElement.style.borderRadius = "8px";

  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.target.set(0, 0, 0);

  // 网格（在 y=planeY 变化时仅作参考，实际平面只参与交点计算）
  grid = new THREE.GridHelper(800, 80, 0x666666, 0x2a2f36);
  grid.position.y = 0;
  scene.add(grid);

  // 坐标轴标识
  axes = new THREE.AxesHelper(16);
  axes.position.set(80, 0.01, 80);
  scene.add(axes);

  scene.add(new THREE.AmbientLight(0xffffff, 0.9));

  // 射线/点位组
  rayGroup = new THREE.Group();
  hitGroup = new THREE.Group();
  scene.add(rayGroup, hitGroup);

  raycaster = new THREE.Raycaster();
  plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);

  // 监听
  window.addEventListener("resize", handleResize);
  controls.addEventListener("change", () => updateDrawing());

  // 初次绘制
  handleResize();
  updateDrawing();

  animate();
}

// ---- 自适应 ----
function handleResize() {
  if (!renderer || !camera) return;
  const w = stage.value.clientWidth,
    h = stage.value.clientHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
  // 同步 HUD 尺寸
  const el = renderer.domElement;
  elTag.value = el.tagName.toLowerCase();
  elW.value = el.clientWidth;
  elH.value = el.clientHeight;
}

// ---- 渲染循环（自动旋转时触发更新）----
function animate() {
  raf = requestAnimationFrame(animate);
  if (autoRotate.value) {
    const t = clock.getElapsedTime();
    camera.position.x = Math.cos(t * 0.25) * 80;
    camera.position.z = Math.sin(t * 0.25) * 80;
    camera.lookAt(0, planeY.value, 0);
    updateDrawing();
  }
  controls.update();
  renderer.render(scene, camera);
}

// ---- 公共动作 ----
function recenter() {
  camera.position.set(60, 60, 60);
  controls.target.set(0, planeY.value, 0);
  controls.update();
  updateDrawing();
}

// 生命周期
onMounted(() => {
  initThree();
});

onBeforeUnmount(() => {
  cancelAnimationFrame(raf);
  window.removeEventListener("resize", handleResize);
  controls?.dispose();
  renderer?.dispose();
  scene?.traverse((obj) => {
    if (obj.geometry) obj.geometry.dispose?.();
    if (obj.material) {
      if (Array.isArray(obj.material))
        obj.material.forEach((m) => m.dispose?.());
      else obj.material.dispose?.();
    }
  });
});

// 选项变化：立即重算
watch([useEdgeMiddles, showRays, intersectionMode, planeY], () =>
  updateDrawing()
);
</script>

<style scoped>
/* 关键：务必给容器高度，否则 el.clientHeight 可能为 0，直接导致“无反馈” */
.wrap {
  width: 100%;
  height: 800px;
  position: relative;
  display: flex;
  flex-direction: column;
}
.toolbar {
  padding: 10px 12px;
  background: #0c0f14;
  color: #d1d5db;
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  border-bottom: 1px solid #1f242d;
  font-size: 13px;
}
.toolbar input,
.toolbar select {
  margin-right: 4px;
}
.btn {
  padding: 4px 10px;
  border: 1px solid #2a2f36;
  background: #151922;
  color: #cbd5e1;
  border-radius: 6px;
  cursor: pointer;
}
.btn:hover {
  background: #1b2030;
}
.stage {
  height: 100%; /* 固定高度，进入页面就有可见画布 */
  width: 100%;
  position: relative;
}
.hud {
  position: absolute;
  right: 14px;
  top: 64px;
  background: rgba(8, 10, 14, 0.78);
  border: 1px solid #222836;
  color: #e2e8f0;
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 12px;
  pointer-events: none;
}
</style>
